home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 2108 / 2108.xpi / components / stylishStyle.js < prev    next >
Text File  |  2009-10-14  |  27KB  |  862 lines

  1. Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
  2.  
  3. function Style() {
  4.     this.id = 0;
  5.     this.url = null;
  6.     this.updateUrl = null;
  7.     this.md5Url = null;
  8.     this.appliedUrl = null;
  9.     this.lastSavedCode = null;
  10.     this.mode = this.CALCULATE_META | this.REGISTER_STYLE_ON_CHANGE;
  11.  
  12.     //these have getters and setters
  13.     this._name = null;
  14.     this._code = null;
  15.     this._enabled = false;
  16.  
  17.     this.meta = [];
  18.  
  19.  
  20.     this.previewOn = false;
  21.     //whether the applied url is yet to be calculated
  22.     this.appliedUrlToBeCalculated = false;
  23. }
  24. Style.prototype = {
  25.  
  26.     /*
  27.         nsISupports
  28.     */
  29.     QueryInterface: XPCOMUtils.generateQI([Components.interfaces.stylishStyle, Components.interfaces.nsIClassInfo, Components.interfaces.nsISupports]),
  30.  
  31.  
  32.     /*
  33.         nsIClassInfo
  34.     */
  35.     getInterfaces: function getInterfaces(aCount) {
  36.         var interfaces = [Components.interfaces.stylishStyle, Components.interfaces.nsIClassInfo, Components.interfaces.nsISupports];
  37.         aCount.value = interfaces.length;
  38.         return interfaces;
  39.     },
  40.     getHelperForLanguage: function getHelperForLanguage(aLanguage) {
  41.         return null;
  42.     },
  43.     classDescription: "Stylish Style",
  44.     classID: Components.ID("{6af398eb-bc14-4001-b40b-4e830b3863b1}"),
  45.     contractID: "@userstyles.org/style;1",
  46.     implementationLanguage: Components.interfaces.nsIProgrammingLanguage.JAVASCRIPT,
  47.     flags: 0,
  48.  
  49.  
  50.     /*
  51.         stylishStyle static methods
  52.     */
  53.     CALCULATE_META: 1,
  54.     REGISTER_STYLE_ON_CHANGE: 2,
  55.     REGISTER_STYLE_ON_LOAD: 4,
  56.     INTERNAL_LOAD_EVENT: 8,
  57.     UNREGISTER_STYLE_ON_LOAD: 16,
  58.  
  59.     list: function(mode, count) {
  60.         var styles = this.findSql("SELECT * FROM styles;", {}, mode);
  61.         count.value = styles.length;
  62.         return styles;
  63.     },
  64.  
  65.     find: function(id, mode, connection) {
  66.         var styles = this.findSql("SELECT * FROM styles WHERE id = :id;", {id: id}, mode, connection);
  67.         return styles.length > 0 ? styles[0] : null;
  68.     },
  69.  
  70.     findByUrl: function(url, mode) {
  71.         var styles = this.findSql("SELECT * FROM styles WHERE url = :url;", {url: url}, mode);
  72.         return styles.length > 0 ? styles[0] : null;
  73.     },
  74.  
  75.     findEnabled: function(enabled, mode, count) {
  76.         var styles = this.findSql("SELECT * FROM styles WHERE enabled = :enabled;", {enabled: enabled}, mode);
  77.         count.value = styles.length;
  78.         return styles;
  79.     },
  80.  
  81.     findForUrl: function(url, includeGlobal, mode, count) {
  82.         var styles = this.list(mode, {});
  83.         styles = styles.filter(function(style) {
  84.             return style.appliesToUrl(url) || (includeGlobal && style.getTypes({}).indexOf("global") > -1);
  85.         });
  86.         count.value = styles.length;
  87.         return styles;
  88.     },
  89.  
  90.     findByMeta: function(name, value, mode, count) {
  91.         var that = this;
  92.         var connection = this.getConnection();
  93.         var statement = connection.createStatement("SELECT style_id FROM style_meta WHERE style_meta.name = :name AND style_meta.value = :value;");
  94.         try {
  95.             this.bind(statement, "name", name);
  96.             this.bind(statement, "value", value);
  97.             var styles = [];
  98.             while (statement.executeStep()) {
  99.                 styles.push(this.find(this.extract(statement, "style_id"), mode, connection));
  100.             }
  101.             count.value = styles.length;
  102.             return styles;
  103.         } catch (ex) {
  104.             Components.utils.reportError(ex);
  105.         } finally {
  106.             statement.reset();
  107.             statement.finalize();
  108.             connection.close();
  109.         }
  110.     },
  111.  
  112.     checkForErrors: function(css, errorListener) {
  113.         var consoleService = Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService);
  114.         consoleService.registerListener(errorListener);
  115.         this.getStyleSheet(css);
  116.         consoleService.unregisterListener(errorListener);
  117.     },
  118.  
  119.     copyListToClipboard: function() {
  120.         function escape(text) {
  121.             return text.replace(/&/g, "&").replace(/>/g, ">").replace(/</g, "<").replace(/"/g, """).replace(/</g, "<");
  122.         }
  123.         var styles = this.list(0, {}).sort(function(a, b) {
  124.             if (a.name > b.name)
  125.                 return 1;
  126.             if (b.name > a.name)
  127.                 return -1;
  128.             return a.id = b.id;
  129.         });
  130.  
  131.         listHTML = "<ul><li>" + styles.map(function(style) {
  132.             var text = "";
  133.             if (style.url)
  134.                 text += "<a href=\"" + escape(style.url) + "\">" + escape(style.name) + "</a>";
  135.             else
  136.                 text += escape(style.name);
  137.             if (!style.enabled)
  138.                 text += " (disabled)";
  139.         }).join("</li><li>") + "</li></ul>";
  140.  
  141.         listText = styles.map(function(style) {
  142.             return "* " + style.name + (style.url ? " <" + style.url + ">" : "") + (style.enabled ? "" : " (disabled)");
  143.         }).join("\n");
  144.  
  145.         var text = Components.classes["@mozilla.org/supports-string;1"].createInstance(Components.interfaces.nsISupportsString);  
  146.         text.data = listText;
  147.  
  148.         var html = Components.classes["@mozilla.org/supports-string;1"].createInstance(Components.interfaces.nsISupportsString);  
  149.         html.data = listHTML;
  150.  
  151.         var trans = Components.classes["@mozilla.org/widget/transferable;1"].createInstance(Components.interfaces.nsITransferable);
  152.         trans.addDataFlavor("text/unicode");  
  153.         trans.setTransferData("text/unicode", text, listText.length * 2);
  154.  
  155.         trans.addDataFlavor("text/html");  
  156.         trans.setTransferData("text/html", html, listHTML.length * 2);
  157.  
  158.         var clipboard = Components.classes["@mozilla.org/widget/clipboard;1"].getService(Components.interfaces.nsIClipboard);  
  159.         clipboard.setData(trans, null, Components.interfaces.nsIClipboard.kGlobalClipboard); 
  160.     },
  161.  
  162.     /*
  163.         stylishStyle instance methods
  164.     */
  165.     init: function(url, updateUrl, md5Url, name, code, enabled, originalCode) {
  166.         //the mode may contain a flag that indicates that this is a load rather than a new style
  167.         var shouldRegister;
  168.         if (this.mode & this.INTERNAL_LOAD_EVENT) {
  169.             this.mode -= this.INTERNAL_LOAD_EVENT;
  170.             shouldRegister = this.shouldRegisterOnLoad();
  171.         } else {
  172.             shouldRegister = this.shouldRegisterOnChange()
  173.         }
  174.         this.initInternal(url, updateUrl, md5Url, name, code, enabled, originalCode, shouldRegister);
  175.     },
  176.  
  177.     get name() {
  178.         return this._name;
  179.     },
  180.  
  181.     set name(name) {
  182.         //reference appliedUrl to make sure it has been calculated before we change the name
  183.         this.appliedUrl;
  184.         this._name = name;
  185.     },
  186.  
  187.     get code() {
  188.         return this._code;
  189.     },
  190.  
  191.     set code(code) {
  192.         this.setCode(code, this.shouldRegisterOnChange());
  193.     },
  194.  
  195.     get enabled() {
  196.         return this._enabled;
  197.     },
  198.  
  199.     set enabled(enabled) {
  200.         if (this.enabled == enabled)
  201.             //no-op
  202.             return;
  203.         if (enabled) {
  204.             if (this.previewOn) {
  205.                 // switch from a preview mode to a normal enabled mode
  206.                 this.previewOn = false;
  207.             } else {
  208.                 this.register();
  209.             }
  210.         } else if (!this.previewOn)
  211.             this.unregister();
  212.         this._enabled = enabled;
  213.     },
  214.  
  215.     delete: function() {
  216.         if (this.id == 0)
  217.             throw "Style can't be deleted; it hasn't been saved.";
  218.         this.unregister();
  219.         var connection = this.getConnection();
  220.         var statement = connection.createStatement("DELETE FROM styles WHERE id = :id;");
  221.         this.bind(statement, "id", this.id);
  222.         try {
  223.             statement.execute();
  224.         } finally {
  225.             statement.reset();
  226.             statement.finalize();
  227.         }
  228.         var statement = connection.createStatement("DELETE FROM style_meta WHERE style_id = :id;");
  229.         this.bind(statement, "id", this.id);
  230.         try {
  231.             statement.execute();
  232.         } finally {
  233.             statement.reset();
  234.             statement.finalize();
  235.             connection.close();
  236.         }
  237.  
  238.         Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService).notifyObservers(this, "stylish-style-delete", null);
  239.  
  240.         this.id = 0;
  241.     },
  242.  
  243.     // the parameter is not passed from external calls
  244.     save: function(reason) {
  245.         var connection = this.getConnection();
  246.         var statement;
  247.         var newStyle = this.id == 0;
  248.  
  249.         var that = this;
  250.         function b(name, value) {
  251.             that.bind(statement, name, value);
  252.         }
  253.         if (this.id == 0) {
  254.             statement = connection.createStatement("INSERT INTO styles (`url`, `updateUrl`, `md5Url`, `name`, `code`, `enabled`, `originalCode`) VALUES (:url, :updateUrl, :md5Url, :name, :code, :enabled, :originalCode);");
  255.         } else {
  256.             statement = connection.createStatement("UPDATE styles SET `url` = :url, `updateUrl` = :updateUrl, `md5Url` = :md5Url, `name` = :name, `code` = :code, `enabled` = :enabled, `originalCode` = :originalCode WHERE `id` = :id;");
  257.             b("id", this.id);
  258.         }
  259.  
  260.         // style is not updatable, original code is useless
  261.         if (!this.updateUrl && !this.md5Url) {
  262.             this.originalCode = null;
  263.             b("originalCode", this.originalCode);
  264.         // original code matches current code, no need to remember original
  265.         } else if (this.originalCode == this.code) {
  266.             this.originalCode = null;
  267.             b("originalCode", this.originalCode);
  268.         // original code exists and is different, don't touch
  269.         } else if (this.originalCode) {
  270.             b("originalCode", this.originalCode);
  271.         // style has changed
  272.         } else if (this.lastSavedCode != this.code) {
  273.             this.originalCode = this.lastSavedCode;
  274.             b("originalCode", this.originalCode);
  275.         } else {
  276.             b("originalCode", null);
  277.         }
  278.  
  279.         b("url", this.url);
  280.         b("updateUrl", this.updateUrl);
  281.         b("md5Url", this.md5Url);
  282.         b("name", this.name);
  283.         b("code", this.code);
  284.         b("enabled", this.enabled);
  285.  
  286.         try {
  287.             statement.execute();
  288.         } catch (ex) {
  289.             statement.reset();
  290.             statement.finalize();
  291.             var err = connection.lastError;
  292.             var text = connection.lastErrorString;
  293.             connection.close();
  294.             if (err == 0)
  295.                 throw ex;
  296.             throw err + " " + text;
  297.         }
  298.         if (newStyle)
  299.             this.id = connection.lastInsertRowID;
  300.         statement.reset();
  301.         statement.finalize();
  302.  
  303.         //the saved code now matches the current code
  304.         this.lastSavedCode = null;
  305.  
  306.         //now reload the metadata
  307.  
  308.         // group this stuff together as a transaction for better performance
  309.         if (this.meta.length > 0) {
  310.             try {
  311.                 connection.beginTransaction();
  312.                 //delete the previous calculated meta data
  313.                 if (!newStyle) {
  314.                     statement = connection.createStatement("DELETE FROM style_meta WHERE style_id = :id;");
  315.                     b("id", this.id);
  316.                     statement.execute();
  317.                     statement.finalize();
  318.                 }
  319.  
  320.                 statement = connection.createStatement("INSERT INTO style_meta (`style_id`, `name`, `value`) VALUES (:id, :name, :value);");
  321.                 this.meta.forEach(function(a) {
  322.                     b("id", that.id);
  323.                     b("name", a[0]);
  324.                     b("value", a[1]);
  325.                     statement.execute();
  326.                 });
  327.                 connection.commitTransaction();
  328.             } finally {
  329.                 statement.reset();
  330.                 statement.finalize();
  331.             }
  332.         }
  333.  
  334.         connection.close();
  335.  
  336.         Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService).notifyObservers(this, "stylish-style-change", reason || null);
  337.     },
  338.  
  339.     appliesToUrl: function(url) {
  340.         if (this.urlRules.some(function(rule) {
  341.             return url == rule;
  342.         }))
  343.             return true;
  344.  
  345.         if (this.urlPrefixRules.some(function(rule) {
  346.             return url.indexOf(rule) == 0;
  347.         }))
  348.             return true;
  349.         var domain;
  350.         //this can throw for weird urls like about:blank
  351.         try {
  352.             domain = this.ios.newURI(url, null, null).host;
  353.         } catch (ex) {
  354.             //Components.utils.reportError("'" + url + "' is not a URL.");
  355.             return false;
  356.         }
  357.         return this.domainRules.some(function(rule) {
  358.             if (rule == domain)
  359.                 return true;
  360.             var i = domain.lastIndexOf("." + rule);
  361.             return i != -1 && (i + 1 + rule.length == domain.length);
  362.         });
  363.     },
  364.  
  365.     //previewing make it so that the code for this style is always applied, even if it's disabled
  366.     setPreview: function(on) {
  367.         if (this.previewOn == on)
  368.             //no-op
  369.             return;
  370.         //if this style is enabled, then preview doesn't really have an effect atm
  371.         if (!this.enabled) {
  372.             //if preview is being turned on, register the style
  373.             if (on)
  374.                 this.register();
  375.             else
  376.                 this.unregister();
  377.         }
  378.         this.previewOn = on;
  379.     },
  380.  
  381.     //set the code back to the saved state
  382.     revert: function() {
  383.         if (this.lastSavedCode) {
  384.             this.code = this.lastSavedCode;
  385.             this.lastSavedCode = null;
  386.         }
  387.     },
  388.  
  389.     addMeta: function(name, value) {
  390.         this.meta.push([name, value]);
  391.     },
  392.  
  393.     removeMeta: function(name, value) {
  394.         this.meta = this.meta.filter(function(e) {
  395.             return e[0] != name || e[1] != value;
  396.         });
  397.     },
  398.  
  399.     removeAllMeta: function(name) {
  400.         this.meta = this.meta.filter(function(e) {
  401.             return e[0] != name;
  402.         });
  403.     },
  404.  
  405.     getMeta: function(name, count) {
  406.         var vals = this.meta.filter(function(e) {
  407.             return e[0] == name;
  408.         }).map(function(e) {
  409.             return e[1];
  410.         });
  411.         count.value = vals.length;
  412.         return vals;
  413.     },
  414.  
  415.     getTypes: function(count) {
  416.         count.value = this.types.length;
  417.         return this.types;
  418.     },
  419.  
  420.     get md5() {
  421.         //https://developer.mozilla.org/en/nsICryptoHash#Computing_the_Hash_of_a_String
  422.         var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
  423.         converter.charset = "UTF-8";
  424.         var result = {};
  425.         var data = converter.convertToByteArray(this.originalCode || this.code, {});
  426.         var ch = Components.classes["@mozilla.org/security/hash;1"].createInstance(Components.interfaces.nsICryptoHash);
  427.         ch.init(ch.MD5);
  428.         ch.update(data, data.length);
  429.         var hash = ch.finish(false);
  430.         function toHexString(charCode) {
  431.             return ("0" + charCode.toString(16)).slice(-2);
  432.         }
  433.         return [toHexString(hash.charCodeAt(i)) for (i in hash)].join("");
  434.     },
  435.  
  436.     checkForUpdates: function() {
  437.         var observerService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
  438.         observerService.notifyObservers(this, "stylish-style-update-check-start", null);
  439.  
  440.         var that = this;
  441.  
  442.         function notifyDone(result) {
  443.             observerService.notifyObservers(that, "stylish-style-update-check-done", result);
  444.         }
  445.  
  446.         function handleFailure() {
  447.             notifyDone("update-check-error");
  448.         }
  449.  
  450.         //if we have a url for a hash, use that
  451.         if (this.md5Url) {
  452.             function handleMd5(text) {
  453.                 if (text == that.md5) {
  454.                     notifyDone("no-update-available");
  455.                 } else {
  456.                     notifyDone("update-available");
  457.                 }
  458.             }
  459.             this.download(this.md5Url, handleMd5, handleFailure);
  460.         //otherwise use the update URL which makes us download the full code
  461.         } else if (this.updateUrl) {
  462.             function handleUpdateUrl(text) {
  463.                 if (text.replace(/\s/g,"") == (that.originalCode || that.code).replace(/\s/g,"")) {
  464.                     notifyDone("no-update-available");
  465.                 } else {
  466.                     notifyDone("update-available");
  467.                 }
  468.             }
  469.             this.download(this.updateUrl, handleUpdateUrl, handleFailure);
  470.         } else {
  471.             notifyDone("no-update-possible");
  472.         }
  473.     },
  474.  
  475.     applyUpdate: function() {
  476.         var observerService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
  477.         observerService.notifyObservers(this, "stylish-style-update-start", null);
  478.  
  479.         var that = this;
  480.  
  481.         function notifyDone(result) {
  482.             observerService.notifyObservers(that, "stylish-style-update-done", result);
  483.         }
  484.  
  485.         function handleFailure() {
  486.             notifyDone("update-failure");
  487.         }
  488.         function handleSuccess(code) {
  489.             that.code = code;
  490.             //we're back to being in sync
  491.             that.originalCode = code;
  492.             that.save("update");
  493.             notifyDone("update-success");
  494.         }
  495.         if (this.updateUrl) {
  496.             this.download(this.updateUrl, handleSuccess, handleFailure);
  497.         } else {
  498.             notifyDone("no-update-possible");
  499.         }
  500.     },
  501.  
  502.     /*
  503.         private
  504.     */
  505.     //can't hard-code because it may not be here when the prototype is created
  506.     get ds() {
  507.         var ds = Components.classes["@userstyles.org/stylish-data-source;1"].getService(Components.interfaces.stylishDataSource)
  508.         this.__defineGetter__("ds", function() {
  509.             return ds;
  510.         });
  511.         return ds;
  512.     },
  513.     HTMLNS: "http://www.w3.org/1999/xhtml",
  514.     ios: Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService),
  515.     sss: Components.classes["@mozilla.org/content/style-sheet-service;1"].getService(Components.interfaces.nsIStyleSheetService),
  516.  
  517.     getStyleSheet: function(code) {
  518.         var parser = Components.classes["@mozilla.org/xmlextras/domparser;1"].createInstance(Components.interfaces.nsIDOMParser);
  519.         var doc1 = parser.parseFromString("<html xmlns='" + this.HTMLNS + "'/>", "application/xhtml+xml");
  520.         var doc = doc1.implementation.createDocument(this.HTMLNS, "stylish-parse", null)
  521.         var style = doc.createElementNS(this.HTMLNS, "style");
  522.         style.appendChild(doc.createTextNode(code));
  523.         doc.documentElement.appendChild(style);
  524.         return doc.styleSheets[0];
  525.  
  526.     },
  527.  
  528.     calculateInternalMeta: function() {
  529.         if (!this.shouldCalculateMeta())
  530.             return;
  531.  
  532.         var sheet = this.getStyleSheet(this._code);
  533.  
  534.         this.removeAllMeta("url");
  535.         this.removeAllMeta("url-prefix");
  536.         this.removeAllMeta("domain");
  537.         this.removeAllMeta("type");
  538.  
  539.         Array.filter(sheet.cssRules, function(rule) {
  540.             return rule instanceof Components.interfaces.nsIDOMCSSMozDocumentRule;
  541.         }).forEach(function (rule) {
  542.             var mozDoc = rule.cssText.substring(0, rule.cssText.indexOf("{") - 1);
  543.             var re = /(url|domain|url-prefix)\s*\([\'\"]?([^)\'\"]+)[\'\"]?\)\s*,?\s*/g;
  544.             var match;
  545.             while ((match = re.exec(mozDoc)) != null) {
  546.                 switch (match[1]) {
  547.                     case "url":
  548.                         this.addMeta('url', match[2]);
  549.                         break;
  550.                     case "url-prefix":
  551.                         this.addMeta('url-prefix', match[2]);
  552.                         break;
  553.                     case "domain":
  554.                         this.addMeta('domain', match[2]);
  555.                         break;
  556.                     default:
  557.                         Components.utils.reportError("Unknown -moz-doc rule type '" + match[1] + "'");
  558.                 }
  559.             }
  560.         }, this);
  561.  
  562.         var namespaces = Array.filter(sheet.cssRules, function(rule) {
  563.             return rule.type == Components.interfaces.nsIDOMCSSRule.UNKNOWN_RULE && rule.cssText.indexOf("@namespace") == 0;
  564.         }).map(function(rule) {
  565.             var text = rule.cssText.replace(/\"/g, "");
  566.             var start = text.indexOf("url(");
  567.             var end = text.lastIndexOf(")");
  568.             return text.substring(start + 4, end);
  569.         });
  570.  
  571.         var hasGlobal = Array.some(sheet.cssRules, function(rule) {
  572.             return rule.type == Components.interfaces.nsIDOMCSSRule.STYLE_RULE;
  573.         });
  574.  
  575.         var appPattern = /^(chrome|about|x-jsd)/;
  576.         var genericPattern = /^[^:]+:?\/*$/; //something like "http:"
  577.         var urlLikeRules = this.urlRules.concat(this.urlPrefixRules);
  578.  
  579.         // global styles have something outside of a moz-doc or a generic moz-doc and have either no namespace or include the html namespace
  580.         if ((hasGlobal && (namespaces.length == 0 || namespaces.indexOf(this.HTMLNS) != -1)) || urlLikeRules.some(function(url) { return genericPattern.test(url) && !appPattern.test(url);}))
  581.             this.addMeta("type", "global");
  582.  
  583.         // app styles have the xul namespace or urls with a specific protocol
  584.         if (namespaces.indexOf("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul") != -1 ||
  585.         urlLikeRules.some(function(url) { return appPattern.test(url); }))
  586.             this.addMeta("type", "app");
  587.  
  588.         // site styles have a domain rule or urls with normal protocols, but not just the protocol only
  589.         if (this.domainRules.length > 0 || urlLikeRules.some(function(url) { return !appPattern.test(url) && !genericPattern.test(url); }))
  590.             this.addMeta("type", "site");
  591.     },
  592.  
  593.     get dataUrl() {
  594.         if (!this.code)
  595.             return null;
  596.         var nameComment = this.name ? "/*" + this.name.replace("*/", "") + "*/" : "";
  597.         // this will strip new lines rather than escape - not what we want
  598.         return this.ios.newURI("data:text/css," + nameComment + this.code.replace(/\n/g, "%0A"), null, null);
  599.     },
  600.  
  601.     register: function() {
  602.         if (!this.stylishOn) {
  603.             return;
  604.         }
  605.         var dataUrl = this.dataUrl;
  606.         if (!dataUrl)
  607.             return;
  608.         this.appliedUrl = dataUrl;
  609.         this.sss.loadAndRegisterSheet(this.appliedUrl, this.sss.AGENT_SHEET);
  610.     },
  611.  
  612.     unregister: function() {
  613.         var unregisterUrl = this.shouldUnregisterOnLoad() ? this.dataUrl : this.appliedUrl;
  614.         if (unregisterUrl == null) {
  615.             return;
  616.         }
  617.         if (this.sss.sheetRegistered(unregisterUrl, this.sss.AGENT_SHEET))
  618.             this.sss.unregisterSheet(unregisterUrl, this.sss.AGENT_SHEET);
  619.         // ignore unregistered styles if stylish isn't on
  620.         else if (this.stylishOn)
  621.             Components.utils.reportError("Stylesheet is supposed to be unregistered, but it's not registered in the first place.");
  622.         this.appliedUrl = null;
  623.     },
  624.  
  625.     bind: function(statement, name, value) {
  626.         var index;
  627.         try {
  628.             index = statement.getParameterIndex(":" + name);
  629.         } catch (ex) {
  630.             if (ex.name == "NS_ERROR_ILLEGAL_VALUE") {
  631.                 index = statement.getParameterIndex(name);
  632.             } else {
  633.                 throw ex;
  634.             }
  635.         }
  636.         if (value === undefined)
  637.             throw "Attempted to bind undefined parameter '" + name + "'";
  638.         else if (value === null)
  639.             statement.bindNullParameter(index);
  640.         else {
  641.             switch(typeof value) {
  642.                 case "string":
  643.                     statement.bindStringParameter(index, value);
  644.                     break;
  645.                 case "number":
  646.                     statement.bindInt32Parameter(index, value);
  647.                     break;
  648.                 case "boolean":
  649.                     statement.bindInt32Parameter(index, value ? 1 : 0);
  650.                     break;
  651.                 default:
  652.                     throw "Unknown value type '" + typeof value + "' for value '" + value + "'";
  653.             }
  654.         }
  655.     },
  656.  
  657.     extract: function(statement, name) {
  658.         var index = statement.getColumnIndex(name);
  659.         var type = statement.getTypeOfIndex(index);
  660.         switch (type) {
  661.             case statement.VALUE_TYPE_NULL:
  662.                 return null;
  663.             case statement.VALUE_TYPE_INTEGER:
  664.                 return statement.getInt32(index);
  665.             case statement.VALUE_TYPE_FLOAT:
  666.                 return statement.getDouble(index);
  667.             case statement.VALUE_TYPE_TEXT:
  668.                 return statement.getString(index);
  669.             case statement.VALUE_TYPE_BLOB:
  670.                 return statement.getBlob(index);
  671.             default:
  672.                 throw "Unrecognized column type " + type;
  673.         }
  674.     },
  675.  
  676.     get appliedUrl() {
  677.         if (this.appliedUrlToBeCalculated) {
  678.             this.appliedUrl = this.dataUrl;
  679.             this.appliedUrlToBeCalculated = false;
  680.         }
  681.         return this._appliedUrl;
  682.     },
  683.  
  684.     set appliedUrl(url) {
  685.         this._appliedUrl = url;
  686.     },
  687.  
  688.     findSql: function(sql, parameters, mode, connection) {
  689.         var closeConnection = false;
  690.         if (!connection) {
  691.             connection = this.getConnection();
  692.             closeConnection = true;
  693.         }
  694.         var statement = connection.createStatement(sql);
  695.         for (i in parameters) {
  696.             this.bind(statement, i, parameters[i]);
  697.         }
  698.         try {
  699.             var that = this;
  700.             function e(name) {
  701.                 return that.extract(statement, name);
  702.             };
  703.             var styles = [];
  704.             var styleMap = [];
  705.             while (statement.executeStep()) {
  706.                 var style = Components.classes["@userstyles.org/style;1"].createInstance(Components.interfaces.stylishStyle);
  707.                 //it makes no sense to calculate meta here because we can load from the db
  708.                 if (mode & this.CALCULATE_META)
  709.                     style.mode = mode - this.CALCULATE_META;
  710.                 else
  711.                     style.mode = mode;
  712.                 // since we can't call initInternal because we're not "inside" the new style, we'll pass a secret flag in the mode
  713.                 style.mode += this.INTERNAL_LOAD_EVENT
  714.                 style.init(e("url"), e("updateUrl"), e("md5Url"), e("name"), e("code"), e("enabled"), e("originalCode"));
  715.                 style.id = e("id");
  716.                 styles.push(style);
  717.                 styleMap[style.id] = style;
  718.             }
  719.         } finally {
  720.             statement.reset();
  721.             statement.finalize();
  722.         }
  723.  
  724.         var styleIds = styles.map(function(style) {
  725.             return style.id;
  726.         });
  727.  
  728.         // fill up the meta
  729.         var statement = connection.createStatement("SELECT * FROM style_meta WHERE style_id IN (" + styleIds.join(",") + ");");
  730.         try {
  731.             while (statement.executeStep()) {
  732.                 styleMap[this.extract(statement, "style_id")].addMeta(this.extract(statement, "name"), this.extract(statement, "value"));
  733.             }
  734.         } finally {
  735.             statement.reset();
  736.             statement.finalize();
  737.         }
  738.  
  739.         //if we turned off CALCULATE_META, turn it back on
  740.         styles.forEach(function(style) {
  741.             if (style.mode != mode)
  742.                 style.mode = mode;
  743.         });
  744.  
  745.         if (closeConnection) {
  746.             connection.close();
  747.         }
  748.  
  749.         return styles;
  750.     },
  751.  
  752.     shouldCalculateMeta: function() {
  753.         return this.mode & this.CALCULATE_META;
  754.     },
  755.  
  756.     shouldRegisterOnChange: function() {
  757.         return this.mode & this.REGISTER_STYLE_ON_CHANGE;
  758.     },
  759.  
  760.     shouldRegisterOnLoad: function() {
  761.         return this.mode & this.REGISTER_STYLE_ON_LOAD;
  762.     },
  763.  
  764.     shouldUnregisterOnLoad: function() {
  765.         return this.mode & this.UNREGISTER_STYLE_ON_LOAD;
  766.     },
  767.  
  768.     setCode: function(code, shouldRegister) {
  769.         //reference appliedUrl to make sure it has been calculated before we change the code
  770.         this.appliedUrl;
  771.         //save the last saved code in case we have to revert
  772.         if (!this.lastSavedCode && this.code && this.id)
  773.             this.lastSavedCode = this.code;
  774.         this._code = code;
  775.         if ((this.enabled || this.previewOn) && shouldRegister) {
  776.             this.unregister();
  777.             this.register();
  778.         }
  779.         this.calculateInternalMeta();
  780.     },
  781.  
  782.     initInternal: function(url, updateUrl, md5Url, name, code, enabled, originalCode, shouldRegister) {
  783.         this.url = url;
  784.         this.updateUrl = updateUrl;
  785.         this.md5Url = md5Url;
  786.         this.name = name;
  787.         this._enabled = enabled;
  788.         this.originalCode = originalCode;
  789.         this.setCode(code, shouldRegister);
  790.         if (!shouldRegister && this.enabled) {
  791.             this.appliedUrlToBeCalculated = true;
  792.         }
  793.         if (this.shouldUnregisterOnLoad()) {
  794.             this.unregister();
  795.         };
  796.     },
  797.  
  798.     get urlRules() {
  799.         return this.getMeta("url", {});
  800.     },
  801.  
  802.     get urlPrefixRules() {
  803.         return this.getMeta("url-prefix", {});
  804.     },
  805.  
  806.     get domainRules() {
  807.         return this.getMeta("domain", {});
  808.     },
  809.  
  810.     get types() {
  811.         return this.getMeta("type", {});
  812.     },
  813.  
  814.     download: function(url, successCallback, failureCallback) {
  815.         var request = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance();
  816.         var me = this;
  817.         // QI the object to nsIDOMEventTarget to set event handlers on it:
  818.         request.QueryInterface(Components.interfaces.nsIDOMEventTarget);
  819.         request.addEventListener("readystatechange", function(event) {
  820.             if (request.readyState == 4) {
  821.                 if ((request.status == 200 || (request.status == 0 && url.indexOf("data:") == 0)) && request.responseText) {
  822.                     successCallback(request.responseText);
  823.                 } else {
  824.                     Components.utils.reportError("Download of '" + url + "' resulted in status " + request.status);
  825.                     failureCallback();
  826.                 }
  827.             }
  828.         }, false);
  829.         // QI it to nsIXMLHttpRequest to open and send the request:
  830.         request.QueryInterface(Components.interfaces.nsIXMLHttpRequest);
  831.         request.open("GET", url, true);
  832.         this.fixXHR(request);
  833.         request.send(null);
  834.     },
  835.  
  836.     fixXHR: function(request) {
  837.         //only a problem on 1.9 toolkit
  838.         var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].getService(Components.interfaces.nsIXULAppInfo);
  839.         var versionChecker = Components.classes["@mozilla.org/xpcom/version-comparator;1"].getService(Components.interfaces.nsIVersionComparator);
  840.         if (versionChecker.compare(appInfo.version, "1.9") >= 0 && Components.classes["@mozilla.org/webshell;1"]) {
  841.             //https://bugzilla.mozilla.org/show_bug.cgi?id=437174
  842.             var ds = Components.classes["@mozilla.org/webshell;1"].createInstance(Components.interfaces.nsIDocShellTreeItem).QueryInterface(Components.interfaces.nsIInterfaceRequestor);
  843.             ds.itemType = Components.interfaces.nsIDocShellTreeItem.typeContent;
  844.             request.channel.loadGroup = ds.getInterface(Components.interfaces.nsILoadGroup);
  845.             request.channel.loadFlags |= Components.interfaces.nsIChannel.LOAD_DOCUMENT_URI;
  846.         }
  847.     },
  848.  
  849.     get stylishOn() {
  850.         return Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefBranch).getBoolPref("extensions.stylish.styleRegistrationEnabled");
  851.     },
  852.  
  853.     getConnection: function() {
  854.         return this.ds.getConnection();
  855.     }
  856. };
  857.  
  858. var components = [Style];
  859. function NSGetModule(compMgr, fileSpec) {
  860.     return XPCOMUtils.generateModule(components);
  861. }
  862.